﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

namespace Util
{
    // V 1.0.1 - 2021-09-28 12:14:50
    // 重构代码 (代码顺序与参数名称，让程序员更容易阅读和理解)
    //
    // V 1.0.0 - 2021-05-27 09:52:56
    // 首次创建，源代码来自于
    // https://github.com/xamarin/Xamarin.Forms/blob/5.0.0/Xamarin.Forms.Core/MessagingCenter.cs

    /// <summary>
    /// 消息中心（事件总线）
    /// </summary>
    public class MessagingCenter : IMessagingCenter
    {
        /// <summary>
        /// 获取 MessagingCenter 的单一实例。
        /// </summary>
        public static IMessagingCenter Instance { get; } = new MessagingCenter();

        class Sender : Tuple<string, Type, Type>
        {
            public Sender(string message, Type senderType, Type argType) : base(message, senderType, argType)
            {
            }
        }

        delegate bool Filter(object sender);

        class MaybeWeakReference
        {
            WeakReference DelegateWeakReference { get; }
            object DelegateStrongReference { get; }

            readonly bool _isStrongReference;

            public MaybeWeakReference(object subscriber, object delegateSource)
            {
                if (subscriber.Equals(delegateSource))
                {
                    // The target is the subscriber; we can use a weakreference
                    DelegateWeakReference = new WeakReference(delegateSource);
                    _isStrongReference = false;
                }
                else
                {
                    DelegateStrongReference = delegateSource;
                    _isStrongReference = true;
                }
            }

            public object Target => _isStrongReference ? DelegateStrongReference : DelegateWeakReference.Target;
            public bool IsAlive => _isStrongReference || DelegateWeakReference.IsAlive;
        }

        class Subscription : Tuple<WeakReference, MaybeWeakReference, MethodInfo, Filter>
        {
            public Subscription(object subscriber, object delegateSource, MethodInfo methodInfo, Filter filter)
                : base(new WeakReference(subscriber), new MaybeWeakReference(subscriber, delegateSource), methodInfo, filter)
            {
            }

            public WeakReference Subscriber => Item1;
            MaybeWeakReference DelegateSource => Item2;
            MethodInfo MethodInfo => Item3;
            Filter Filter => Item4;

            public void InvokeCallback(object sender, object args)
            {
                if (!Filter(sender))
                {
                    return;
                }

                if (MethodInfo.IsStatic)
                {
                    MethodInfo.Invoke(null, MethodInfo.GetParameters().Length == 1 ? new[] { sender } : new[] { sender, args });
                    return;
                }

                var target = DelegateSource.Target;

                if (target == null)
                {
                    return; // Collected 
                }

                MethodInfo.Invoke(target, MethodInfo.GetParameters().Length == 1 ? new[] { sender } : new[] { sender, args });
            }

            public bool CanBeRemoved()
            {
                return !Subscriber.IsAlive || !DelegateSource.IsAlive;
            }
        }

        readonly Dictionary<Sender, List<Subscription>> _subscriptions = new Dictionary<Sender, List<Subscription>>();

        #region Subscribe

        /// <summary>
        /// 在 subscriber 上运行 callback 以响应名为 message 且由 source 创建的消息。
        /// </summary>
        /// <typeparam name="TSender">发送消息的对象的类型。</typeparam>
        /// <param name="subscriber">订阅消息的对象。 通常情况下，这是使用订阅对象中使用的 this 关键字指定的。</param>
        /// <param name="message">将发送给正在从类型 TSender 的实例侦听消息的对象的消息。</param>
        /// <param name="callback">一个回调，它将发送方和实参作为形参，在订阅方收到消息时运行。</param>
        /// <param name="source">将发送消息的对象。</param>
        public static void Subscribe<TSender>(object subscriber, string message, Action<TSender> callback, TSender source = null) where TSender : class
        {
            Instance.Subscribe(subscriber, message, callback, source);
        }

        /// <summary>
        /// 在 subscriber 上运行 callback 以响应名为 message 且由 source 创建的消息。
        /// </summary>
        /// <typeparam name="TSender">发送消息的对象的类型。</typeparam>
        /// <param name="subscriber">订阅消息的对象。 通常情况下，这是使用订阅对象中使用的 this 关键字指定的。</param>
        /// <param name="message">将发送给正在从类型 TSender 的实例侦听消息的对象的消息。</param>
        /// <param name="callback">一个回调，它将发送方和实参作为形参，在订阅方收到消息时运行。</param>
        /// <param name="source">将发送消息的对象。</param>
        void IMessagingCenter.Subscribe<TSender>(object subscriber, string message, Action<TSender> callback, TSender source)
        {
            if (subscriber == null)
                throw new ArgumentNullException(nameof(subscriber));
            if (callback == null)
                throw new ArgumentNullException(nameof(callback));

            var target = callback.Target;

            Filter filter = sender =>
            {
                var send = (TSender)sender;
                return (source == null || send == source);
            };

            InnerSubscribe(subscriber, message, typeof(TSender), null, target, callback.Method, filter);
        }

        /// <summary>
        /// 在 subscriber 上运行 callback 以响应名为 message 且由 source 创建的参数化消息。
        /// </summary>
        /// <typeparam name="TSender">发送消息的对象的类型。</typeparam>
        /// <typeparam name="TArgs">用作消息的消息参数的对象的类型。</typeparam>
        /// <param name="subscriber">订阅消息的对象。 通常情况下，这是使用订阅对象中使用的 this 关键字指定的。</param>
        /// <param name="message">将发送给正在从类型 TSender 的实例侦听消息的对象的消息。</param>
        /// <param name="callback">一个回调，它将发送方和实参作为形参，在订阅方收到消息时运行。</param>
        /// <param name="source">将发送消息的对象。</param>
        public static void Subscribe<TSender, TArgs>(object subscriber, string message, Action<TSender, TArgs> callback, TSender source = null) where TSender : class
        {
            Instance.Subscribe(subscriber, message, callback, source);
        }

        /// <summary>
        /// 在 subscriber 上运行 callback 以响应名为 message 且由 source 创建的参数化消息。
        /// </summary>
        /// <typeparam name="TSender">发送消息的对象的类型。</typeparam>
        /// <typeparam name="TArgs">用作消息的消息参数的对象的类型。</typeparam>
        /// <param name="subscriber">订阅消息的对象。 通常情况下，这是使用订阅对象中使用的 this 关键字指定的。</param>
        /// <param name="message">将发送给正在从类型 TSender 的实例侦听消息的对象的消息。</param>
        /// <param name="callback">一个回调，它将发送方和实参作为形参，在订阅方收到消息时运行。</param>
        /// <param name="source">将发送消息的对象。</param>
        void IMessagingCenter.Subscribe<TSender, TArgs>(object subscriber, string message, Action<TSender, TArgs> callback, TSender source)
        {
            if (subscriber == null)
                throw new ArgumentNullException(nameof(subscriber));
            if (callback == null)
                throw new ArgumentNullException(nameof(callback));

            var target = callback.Target;

            Filter filter = sender =>
            {
                var send = (TSender)sender;
                return (source == null || send == source);
            };

            InnerSubscribe(subscriber, message, typeof(TSender), typeof(TArgs), target, callback.Method, filter);
        }

        #endregion

        #region Unsubscribe

        /// <summary>
        /// 取消订阅指定的无参数订阅服务器消息。
        /// </summary>
        /// <typeparam name="TSender">发送消息的对象的类型。</typeparam>
        /// <param name="subscriber">订阅消息的对象。 通常情况下，这是使用订阅对象中使用的 this 关键字指定的。</param>
        /// <param name="message">将发送给正在从类型 TSender 的实例侦听消息的对象的消息。</param>
        public static void Unsubscribe<TSender>(object subscriber, string message) where TSender : class
        {
            Instance.Unsubscribe<TSender>(subscriber, message);
        }

        /// <summary>
        /// 取消订阅指定的无参数订阅服务器消息。
        /// </summary>
        /// <typeparam name="TSender">发送消息的对象的类型。</typeparam>
        /// <param name="subscriber">订阅消息的对象。 通常情况下，这是使用订阅对象中使用的 this 关键字指定的。</param>
        /// <param name="message">将发送给正在从类型 TSender 的实例侦听消息的对象的消息。</param>
        void IMessagingCenter.Unsubscribe<TSender>(object subscriber, string message)
        {
            InnerUnsubscribe(message, typeof(TSender), null, subscriber);
        }

        /// <summary>
        /// 取消订阅来自指定发件人的指定消息的订阅服务器。
        /// </summary>
        /// <typeparam name="TSender">发送消息的对象的类型。</typeparam>
        /// <typeparam name="TArgs">用作消息的消息参数的对象的类型。</typeparam>
        /// <param name="subscriber">订阅消息的对象。 通常情况下，这是使用订阅对象中使用的 this 关键字指定的。</param>
        /// <param name="message">将发送给正在从类型 TSender 的实例侦听消息的对象的消息。</param>
        public static void Unsubscribe<TSender, TArgs>(object subscriber, string message) where TSender : class
        {
            Instance.Unsubscribe<TSender, TArgs>(subscriber, message);
        }

        /// <summary>
        /// 取消订阅来自指定发件人的指定消息的订阅服务器。
        /// </summary>
        /// <typeparam name="TSender">发送消息的对象的类型。</typeparam>
        /// <typeparam name="TArgs">用作消息的消息参数的对象的类型。</typeparam>
        /// <param name="subscriber">订阅消息的对象。 通常情况下，这是使用订阅对象中使用的 this 关键字指定的。</param>
        /// <param name="message">将发送给正在从类型 TSender 的实例侦听消息的对象的消息。</param>
        void IMessagingCenter.Unsubscribe<TSender, TArgs>(object subscriber, string message)
        {
            InnerUnsubscribe(message, typeof(TSender), typeof(TArgs), subscriber);
        }

        #endregion

        #region Send

        /// <summary>
        /// 发送没有参数的命名消息。
        /// </summary>
        /// <typeparam name="TSender">发送消息的对象的类型。</typeparam>
        /// <param name="sender">发送消息的实例。 通常情况下，这是使用发送对象中使用的 this 关键字指定的。</param>
        /// <param name="message">将发送给正在从类型 TSender 的实例侦听消息的对象的消息。</param>
        public static void Send<TSender>(TSender sender, string message) where TSender : class
        {
            Instance.Send(sender, message);
        }

        /// <summary>
        /// 发送没有参数的命名消息。
        /// </summary>
        /// <typeparam name="TSender">发送消息的对象的类型。</typeparam>
        /// <param name="sender">发送消息的实例。 通常情况下，这是使用发送对象中使用的 this 关键字指定的。</param>
        /// <param name="message">将发送给正在从类型 TSender 的实例侦听消息的对象的消息。</param>
        void IMessagingCenter.Send<TSender>(TSender sender, string message)
        {
            if (sender == null)
                throw new ArgumentNullException(nameof(sender));
            InnerSend(message, typeof(TSender), null, sender, null);
        }

        /// <summary>
        /// 使用指定的参数发送命名消息。
        /// </summary>
        /// <typeparam name="TSender">发送消息的对象的类型。</typeparam>
        /// <typeparam name="TArgs">用作消息的消息参数的对象的类型。</typeparam>
        /// <param name="sender">发送消息的实例。 通常情况下，这是使用发送对象中使用的 this 关键字指定的。</param>
        /// <param name="message">将发送给正在从类型 TSender 的实例侦听消息的对象的消息。</param>
        /// <param name="args">将传递给侦听器的回调的参数。</param>
        public static void Send<TSender, TArgs>(TSender sender, string message, TArgs args) where TSender : class
        {
            Instance.Send(sender, message, args);
        }

        /// <summary>
        /// 使用指定的参数发送命名消息。
        /// </summary>
        /// <typeparam name="TSender">发送消息的对象的类型。</typeparam>
        /// <typeparam name="TArgs">用作消息的消息参数的对象的类型。</typeparam>
        /// <param name="sender">发送消息的实例。 通常情况下，这是使用发送对象中使用的 this 关键字指定的。</param>
        /// <param name="message">将发送给正在从类型 TSender 的实例侦听消息的对象的消息。</param>
        /// <param name="args">将传递给侦听器的回调的参数。</param>
        void IMessagingCenter.Send<TSender, TArgs>(TSender sender, string message, TArgs args)
        {
            if (sender == null)
                throw new ArgumentNullException(nameof(sender));
            InnerSend(message, typeof(TSender), typeof(TArgs), sender, args);
        }

        #endregion

        #region Inner Subscribe / InnerUnsubscribe / InnerSend

        void InnerSubscribe(object subscriber, string message, Type senderType, Type argType, object target, MethodInfo methodInfo, Filter filter)
        {
            if (message == null)
                throw new ArgumentNullException(nameof(message));
            var key = new Sender(message, senderType, argType);
            var value = new Subscription(subscriber, target, methodInfo, filter);
            if (_subscriptions.ContainsKey(key))
            {
                _subscriptions[key].Add(value);
            }
            else
            {
                var list = new List<Subscription> { value };
                _subscriptions[key] = list;
            }
        }

        void InnerUnsubscribe(string message, Type senderType, Type argType, object subscriber)
        {
            if (subscriber == null)
                throw new ArgumentNullException(nameof(subscriber));
            if (message == null)
                throw new ArgumentNullException(nameof(message));

            var key = new Sender(message, senderType, argType);
            if (!_subscriptions.ContainsKey(key))
                return;
            _subscriptions[key].RemoveAll(sub => sub.CanBeRemoved() || sub.Subscriber.Target == subscriber);
            if (!_subscriptions[key].Any())
                _subscriptions.Remove(key);
        }

        void InnerSend(string message, Type senderType, Type argType, object sender, object args)
        {
            if (message == null)
                throw new ArgumentNullException(nameof(message));
            var key = new Sender(message, senderType, argType);
            if (!_subscriptions.ContainsKey(key))
                return;
            List<Subscription> subcriptions = _subscriptions[key];
            if (subcriptions == null || !subcriptions.Any())
                return; // should not be reachable

            // ok so this code looks a bit funky but here is the gist of the problem. It is possible that in the course
            // of executing the callbacks for this message someone will subscribe/unsubscribe from the same message in
            // the callback. This would invalidate the enumerator. To work around this we make a copy. However if you unsubscribe 
            // from a message you can fairly reasonably expect that you will therefor not receive a call. To fix this we then
            // check that the item we are about to send the message to actually exists in the live list.
            List<Subscription> subscriptionsCopy = subcriptions.ToList();
            foreach (Subscription subscription in subscriptionsCopy)
            {
                if (subscription.Subscriber.Target != null && subcriptions.Contains(subscription))
                {
                    subscription.InvokeCallback(sender, args);
                }
            }
        }

        // This is a bit gross; it only exists to support the unit tests in PageTests
        // because the implementations of ActionSheet, Alert, and IsBusy are all very
        // tightly coupled to the MessagingCenter singleton 
        internal static void ClearSubscribers()
        {
            (Instance as MessagingCenter)?._subscriptions.Clear();
        }

        #endregion
    }
}
